package com.youlu.campus.admin.cert;

import cn.hutool.core.date.DateUtil;
import com.google.common.util.concurrent.*;
import com.youlu.campus.admin.cert.dto.ActivityTaskTempDTO;
import com.youlu.campus.admin.cert.dto.UserOrderCertDTO;
import com.youlu.campus.base.exception.BusinessException;
import com.youlu.campus.base.order.vo.OrderRequest;
import com.youlu.campus.component.QiniuConfig;
import com.youlu.campus.component.QiniuUtil;
import com.youlu.campus.entity.*;
import com.youlu.campus.entity.enums.ItemTypeEnum;
import com.youlu.campus.service.file.FileExportTaskService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileInputStream;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author : [dengbo]
 * @className : OrderCertService
 * @description : [描述说明该类的功能]
 * @createTime : [2023/4/1 11:05]
 */
@Service
@Slf4j
public class OrderCertService {
    public static final ExecutorService executorService = Executors.newCachedThreadPool();

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private CertMake certMake;

    @Autowired
    private FileExportTaskService fileExportTaskService;

    @Autowired
    private QiniuUtil qiniuUtil;

    @Autowired
    private QiniuConfig qiniuConfig;

    /**
     *  纸质证书导出查询条件构建、 拷贝的之前的
     * @param request
     * @return
     */
    private Query getOrderQuery(OrderRequest request) {
        Query query = new Query();
        Criteria criteriaPay = null;
        if (StringUtils.isNotBlank(request.getStatus())) {
            query.addCriteria(Criteria.where("status").is(request.getStatus()));
        } else if (CollectionUtils.isNotEmpty(request.getStatuses())) {
            query.addCriteria(Criteria.where("status").in(request.getStatuses()));
        } else if (CollectionUtils.isNotEmpty(request.getNotStatuses())) {
            query.addCriteria(Criteria.where("status").nin(request.getNotStatuses()));
        }
        if (StringUtils.isNotBlank(request.getActivityId())) {
            query.addCriteria(Criteria.where("activityId").is(request.getActivityId()));
        }
        if (StringUtils.isNotBlank(request.getUniversityId())) {
            query.addCriteria(Criteria.where("universityId").is(request.getUniversityId()));
        }
        if (StringUtils.isNotBlank(request.getOrderNo())) {
            query.addCriteria(Criteria.where("orderNo").is(request.getOrderNo()));
        }
        if (StringUtils.isNotBlank(request.getMobile())) {
            query.addCriteria(Criteria.where("mobile").is(request.getMobile()));
        }
        if (StringUtils.isNotBlank(request.getName())) {
            query.addCriteria(Criteria.where("name").is(request.getName()));
        }
        if (Objects.nonNull(request.getVerifyStatus())) {
            query.addCriteria(Criteria.where("verifyStatus").is(request.getVerifyStatus()));
        }
        if (StringUtils.isNotBlank(request.getTagsSearchStr())) {
            Pattern pattern = Pattern.compile("^.*" + request.getTagsSearchStr() + ".*$", Pattern.CASE_INSENSITIVE);
            query.addCriteria(Criteria.where("tagsSearchStr").regex(pattern));
        }
        if (request.getStartTime() != null || request.getEndTime() != null) {
            criteriaPay = Criteria.where("payTime");
            if (request.getStartTime() != null) {
                criteriaPay.gte(request.getStartTime());
            }
            if (request.getEndTime() != null) {
                criteriaPay.lte(request.getEndTime());
            }
        }
        if (!"0".equals(request.getRefundStatus()) && StringUtils.isNotBlank(request.getRefundStatus())) {
            query.addCriteria(Criteria.where("refundStatus").is(request.getRefundStatus()));
        }
        if (StringUtils.isNotBlank(request.getTransationId())) {
            query.addCriteria(Criteria.where("transactionId").is(request.getTransationId()));
        }
        if (StringUtils.isNotBlank(request.getLogisticsNo())) {
            List<String> logisticsNoList = Arrays.asList(request.getLogisticsNo());
            query.addCriteria(Criteria.where("logisticsNos").in(logisticsNoList));
        }
        if (StringUtils.isNotBlank(request.getSignUpName())) {
            query.addCriteria(Criteria.where("signUpName").is(request.getSignUpName()));
        }
        if (StringUtils.isNotBlank(request.getSignUpMobile())) {
            query.addCriteria(Criteria.where("signUpMobile").is(request.getSignUpMobile()));
        }
        if (StringUtils.isNotBlank(request.getRefundApplyUserType())) {
            query.addCriteria(Criteria.where("refundApplyUserType").is(request.getRefundApplyUserType()));
        }
        if (Objects.nonNull(criteriaPay)) {
            query.addCriteria(criteriaPay);
        }
        if (StringUtils.isNotBlank(request.getRefundWay())) {
            query.addCriteria(Criteria.where("refundWay").is(request.getRefundWay()));
        }
        if (StringUtils.isNotBlank(request.getRefundPayChannel())) {
            query.addCriteria(Criteria.where("refundPayChannel").is(request.getRefundPayChannel()));
        }
        if (StringUtils.isNotBlank(request.getCategory())) {
            if ("ca".equals(request.getCategory())) {
                query.addCriteria(Criteria.where("category").is(null));
            } else {
                query.addCriteria(Criteria.where("category").is(request.getCategory()));
            }
        }
        if (StringUtils.isNotBlank(request.getAppId())) {
            query.addCriteria(Criteria.where("appId").is(request.getAppId()));
        }
        if (Objects.nonNull(request.getStartRefundTime()) && Objects.nonNull(request.getEndRefundTime())) {
            query.addCriteria(Criteria.where("refundTime").gte(request.getStartRefundTime()).lte(request.getEndRefundTime()));
        }
        return query;
    }

    /**
     * 纸质证书导出
     * @param request
     * @param loginName
     * @param userId
     */
    public void orderCertMakeAndExport(OrderRequest request, String loginName, String userId) {
        if (StringUtils.isBlank(request.getAppId()) || !"wxc3211a2e49489560".equals(request.getAppId())) {
            throw new BusinessException("请选择小程序订单导出");
        }
        Query taskQuery = new Query(Criteria.where("optUserId").is(userId).and("bizType").is("pcert").and("status").is(0));
        // 导出任务是否存在
        boolean existsTask = mongoTemplate.exists(taskQuery, FileExportTask.class);
        if (existsTask) {
            throw new BusinessException("当前用户存在导出的任务");
        }

        Query query = this.getOrderQuery(request);
        Long count = mongoTemplate.count(query, OrderInfo.class);
        if (count != null && count > 2000) {
            throw new BusinessException("证书订单数量不能大于2000");
        }

        // 查询导出订单活动id去重
        List<OrderInfo> orderPOS = mongoTemplate.find(query, OrderInfo.class);
        List<String> aidList = orderPOS.stream().map(OrderInfo::getActivityId).distinct().collect(Collectors.toList());

        //活动任务模版
        // 活动 + 证书类型 + 等级 => 确认唯一模板 然后存储起来
        Map<String, ActivityTaskTempDTO> templateMap = new HashMap<>();
        Map<String, String> activityInfoMap = new HashMap<>();
        Map<String, CaTemplate> templateHashMap = new HashMap<>();
        List<CaCertificateConfig> configList = mongoTemplate.find(new Query(Criteria.where("activityId").in(aidList)), CaCertificateConfig.class);
        configList.stream().forEach(item -> {
            String key = getAidBizTypeTemKey(item.getActivityId(), item.getCaType(), item.getCaLevelName());
            ActivityTaskTempDTO dto = templateMap.get(key);
            if (dto == null) {
                dto = new ActivityTaskTempDTO();
                String activityName = activityInfoMap.get(item.getActivityId());
                if (activityName == null) {
                    ActivityInfo activityInfo = mongoTemplate.findById(item.getActivityId(), ActivityInfo.class);
                    activityName = activityInfo.getName();
                    activityInfoMap.put(item.getActivityId(), activityName);
                }
                // 查询模版
                CaTemplate caTemplate = templateHashMap.get(item.getTemplateId());
                if (caTemplate == null) {
                    caTemplate = getCertTmpById(item.getTemplateId());
                    templateHashMap.put(item.getTemplateId(), caTemplate);
                }
                dto.setCaTemplate(caTemplate);
                dto.setActivityName(activityName);
                dto.setActivityId(item.getActivityId());
                templateMap.put(key, dto);
            }
        });
        // 过滤未支付和不是证书的订单
        List<UserOrderCertDTO> list = orderPOS.stream().filter(item -> !"0".equals(item.getStatus()))
                .flatMap(orderInfo -> {
                    List<OOrderInfo> orderInfos = mongoTemplate.find(new Query(Criteria.where("orderNo").is(orderInfo.getOrderNo())), OOrderInfo.class);
                    return orderInfos.stream().filter(this::isCert).map(subOrder -> {
                        UserOrderCertDTO dto = new UserOrderCertDTO();
                        CaInfo caInfo = subOrder.getCaInfo();
                        dto.setCertNo(subOrder.getCertificateNo());
                        dto.setCertVersion(caInfo.getEdition() == null ? "" : caInfo.getEdition());
                        dto.setUserName(orderInfo.getCaPrintName());
                        dto.setSchoolName(orderInfo.getUniversityName());
                        if (orderInfo.getPayTime() == null) {
                            orderInfo.setPayTime(new Date());
                        }
                        String payTime = DateUtil.format(orderInfo.getPayTime(),"yyyyMMddHHmmss");
                        dto.setPayTime(payTime);
                        dto.setActivityName("");
                        // 模版key
                        String key = getAidBizTypeTemKey(orderInfo.getActivityId(), orderInfo.getTaskBizType(), caInfo.getLevel());
                        ActivityTaskTempDTO taskTempDTO = templateMap.get(key);
                        if (taskTempDTO != null) {
                            CaTemplate caTemplate = taskTempDTO.getCaTemplate();
                            dto.setCaTemplate(caTemplate);
                            dto.setActivityName(taskTempDTO.getActivityName());
                            UserCaRecord userCaRecord = getCaRecordByCnoAndAid(subOrder.getCertificateNo(), orderInfo.getActivityId());
                            if (userCaRecord != null && StringUtils.isNotBlank(userCaRecord.getCaTime())) {
                                Date pubDate = DateUtil.parseDate(userCaRecord.getCaTime());
                                String pubTime  = DateUtil.format(pubDate,"yyyy年M月d日");
                                dto.setPublishTime(pubTime);
                            } else {
                                dto.setPublishTime(DateUtil.format(new Date(),"yyyy年M月d日"));
                            }
                        }
                        String fileName = String.join("-", dto.getActivityName(), dto.getCertVersion(), dto.getPayTime(), dto.getUserName());
                        dto.setFileName(fileName);
                        return dto;
                    });

                }).collect(Collectors.toList());

        int num = list.size();
        FileExportTask fileExportTask = new FileExportTask();
        fileExportTask.setToken(loginName);
        fileExportTask.setBizType("pcert");
        fileExportTask.setStatus(0);
        fileExportTask.setOptUser(loginName);
        fileExportTask.setOptUserId(userId);
        fileExportTask.setNum(num);
        fileExportTask.setFileName("订单生成纸制证书.zip");
        fileExportTask.setDesc("订单纸质证书导出");
        String fileTaskId = fileExportTaskService.create(fileExportTask);
        // 任务id
        if (fileTaskId == null) {
            throw new BusinessException("生成任务Id失败");
        }
        String fileName = fileTaskId + ".zip";
        Map<String, List<UserOrderCertDTO>> map = list.stream().filter(item -> item.getCaTemplate() != null).collect(Collectors.groupingBy(item -> item.getCaTemplate().getId()));
        ListeningExecutorService listeningDecorator = MoreExecutors.listeningDecorator(executorService);
        // 提交任务
        ListenableFuture<Pair<String, Integer>> future = listeningDecorator.submit(() -> {
            Pair<String, Integer> picInfo = certMake.makeCertPicByTemplete(fileTaskId, map);
            String zipFile = picInfo.getKey();
            File file = new File(zipFile);
            FileInputStream stream = new FileInputStream(file);
            String newFileName = "pcert-zip-" + fileName;
            String url = qiniuUtil.uploadFile(stream, newFileName);
            String domain = qiniuConfig.getDomainOfBucket();
            return Pair.of(domain + "/" + url, picInfo.getRight());
        });

        // 注册callback
        Futures.addCallback(future, new FutureCallback<Pair<String, Integer>>() {
            @Override
            public void onSuccess(@Nullable Pair<String, Integer> pair) {
                String uploadFile = pair.getKey();
                Integer num = pair.getRight();
                log.info("taskId:{} run success zip file url:{},export num:{}", fileTaskId, uploadFile, num);
                updateTaskStatus(fileTaskId, uploadFile, fileName, null, 1, pair.getRight());
            }

            @Override
            public void onFailure(Throwable throwable) {
                log.error("taskId:{} run fail", fileTaskId, throwable);
                updateTaskStatus(fileTaskId, "", fileName, throwable.getMessage(), 5, 0);
            }
        }, listeningDecorator);
    }

    /**
     * 更新导出任务状态
     * @param fileTaskId
     * @param url
     * @param fileName
     * @param errMsg
     * @param status
     * @param exportNum
     */
    public void updateTaskStatus(String fileTaskId, String url, String fileName, String errMsg, Integer status, Integer exportNum) {
        Query query = new Query().addCriteria(Criteria.where("id").is(fileTaskId));
        Update update = new Update();
        update.set("status", status);
        update.set("url", url);
        update.set("fileName", fileName);
        update.set("updatedTime", new Date());
        update.set("errorMsg", errMsg);
        update.set("exportedNum", exportNum);
        mongoTemplate.updateFirst(query, update, FileExportTask.class);
    }

    public String getAidBizTypeTemKey(String activityId, String taskBizType, String levelName) {
        log.info("get temp key ,activityId:{},biz-type:{},level", activityId, taskBizType, levelName);
        if (StringUtils.isBlank(levelName)) {
            return String.join("#", activityId, taskBizType);
        }
        return String.join("#", activityId, taskBizType, levelName);
    }

    public CaTemplate getCertTmpById(String tempId) {
        CaTemplate re = mongoTemplate.findOne(new Query(Criteria.where("id").is(tempId)), CaTemplate.class);
        return re;
    }

    public UserCaRecord getCaRecordByCnoAndAid(String certificateNo, String activityId) {
        Query query = new Query(Criteria.where("certificateNo").is(certificateNo).and("activityId").is(activityId));
        return mongoTemplate.findOne(query, UserCaRecord.class);
    }

    public CertificateActivityConfig getCertConfigById(String certificateId) {
        CertificateActivityConfig re = mongoTemplate.findOne(new Query(Criteria.where("id").is(certificateId)), CertificateActivityConfig.class);
        return re;
    }

    /**
     * 是否是证书判断
     * @param oorderInfo
     * @return
     */
    public boolean isCert(OOrderInfo oorderInfo) {
        // 新的逻辑
        if (oorderInfo.getCaItemType() != null) {
            if (oorderInfo.getCaItemType() == ItemTypeEnum.CA_AND_GIFT.getValue() || oorderInfo.getCaItemType() == ItemTypeEnum.CA.getValue()) {
                return true;
            }
            return false;
        }
        // 老的逻辑
        if (oorderInfo.getCaGift() == null || !oorderInfo.getCaGift()) {
            return true;
        }
        return false;
    }
}
